#include "write.h"

#include <cassert> // assert
#include <limits> // std::numeric_limits

#include <unistd.h> // write pwrite
#include <errno.h> // errno E*

#include "simple/support/algorithm/utils.hpp" // support::distance

namespace simple::io
{

	template <typename Result, typename Write, typename Error>
	Result write_impl(Write write_f, int fd, byte_view data, Error make_error) noexcept
	{
		assert(data.valid());
		assert(data.begin() != nullptr && data.end() != nullptr);

		auto result = write_f(fd, data.begin(), distance(data));
		if(-1 == result)
		{
			switch(errno)
			{
#if EWOULDBLOCK != EAGAIN
				case EWOULDBLOCK: return data.begin();
#endif
				case EAGAIN: return data.begin();
				case EFBIG: return size_limit_reached{errno};
				case ENOSPC: return no_space_to_write{errno};
				case ENOMEM: case ENOBUFS: return system_out_of_resources_to_write{errno};
				case ENXIO: return bad_write_to_device{errno};
				case EBADF: return not_write_handle{errno};

				default: return make_error();
			}
		}

		return data.begin() + result;
	}

	template <typename Result, typename Write, typename Error>
	Result write_impl(Write write_f, const unique_handle& handle, byte_view data, Error make_error) noexcept
	{
		assert(handle != nullptr);
		int fd = static_cast<int>(static_cast<enum handle>(handle.get()));
		return write_impl<Result>(write_f, fd, data, make_error);
	}

	ex_write_result write_only_errors()
	{
		switch(errno)
		{
			case EPIPE: return bad_write_to_pipe{errno};
			case ECONNRESET: return socket_not_connected{errno};
			case EACCES: return write_access_denied{errno};
			case ENETDOWN: return network_interface_down{errno};
			case ENETUNREACH: return network_unreachable{errno};
			default: return error{errno};
		}
	};

	ex_write_result write(const unique_handle& handle, byte_view data) noexcept
	{ return write_impl<ex_write_result>(&::write, handle, data, write_only_errors); }

	auto get_pwrite(std::size_t offset)
	{
		assert(std::numeric_limits<off_t>::max() >= offset);
		return [offset](int fd, const void* buf, size_t nbyte)
			{ return ::pwrite(fd, buf, nbyte, offset); };
	}

	ex_write_offset_result pwrite_only_errors()
	{
		switch(errno)
		{
			case ESPIPE: return not_seekable{errno};
			default: return error{errno};
		}
	};

	ex_write_offset_result write(const unique_handle& hndl, byte_view bv, std::size_t off) noexcept
	{ return write_impl<ex_write_offset_result>(get_pwrite(off), hndl, bv, pwrite_only_errors); }

	ex_write_result write(standard_output_t so, byte_view bv) noexcept
	{ return write_impl<ex_write_result>(&::write, so.value, bv, write_only_errors); }

	ex_write_result write(standard_error_t se, byte_view bv) noexcept
	{ return write_impl<ex_write_result>(&::write, se.value, bv, write_only_errors); }

	ex_write_offset_result write(standard_output_t so, byte_view bv, std::size_t off) noexcept
	{ return write_impl<ex_write_offset_result>(get_pwrite(off), so.value, bv, pwrite_only_errors); }

	ex_write_offset_result write(standard_error_t se, byte_view bv, std::size_t off) noexcept
	{ return write_impl<ex_write_offset_result>(get_pwrite(off), se.value, bv, pwrite_only_errors); }


} // namespace simple::io
